﻿using SoftwareConsulting.BI11.NumericalMethods.Statistics;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс быстрого расчета статистических моментов (ускорение за счет точности)
    /// </summary>
    public class StatisticalMomentsFast : StatisticalMoments
    {
        /// <summary>
        /// Конструктор класса по умолчанию
        /// </summary>
        public StatisticalMomentsFast() : 
            base()
        {
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="momentsCount">количество хранимых моментов</param>
        public StatisticalMomentsFast(int momentsCount) : 
            base(momentsCount)
        {
        }

        /// <summary>
        /// Сохраняет случайную переменную
        /// </summary>
        /// <param name="x"></param>
        public override void Accumulate(double x)
        {            
            double value = 1.0;
            for (int n = 0; n < Moments.Length; n++)
            {
                Moments[n] += value;
                value *= x;
            }
        }

        /// <summary>
        /// Возвращает среднее
        /// </summary>
        public override double Average
        {
            get 
            {
                return Moments[1] / Moments[0]; 
            }
        }

        /// <summary>
        /// Возвращает эксцесс (резкость распределения в области максимума)
        /// </summary>
        /// <remarks>Эксцесс нормального распределения, по определению, это ноль</remarks>
        public override double Kurtosis
        {
            get
            {
                if (Moments[0] < 4)
                    return double.NaN;
                double x1 = this.Average;
                double x2 = Moments[2] / Moments[0];
                double x3 = Moments[3] / Moments[0];
                double x4 = Moments[4] / Moments[0];
                double xSquared = x1 * x1;
                double m4 = x4 - (4 * x1 * x3) + 3 * xSquared
                                                            * (2 * x2 - xSquared);
                double kFact = Moments[0] * (Moments[0] + 1)
                                        / ((Moments[0] - 1) * (Moments[0] - 2)
                                                            * (Moments[0] - 3));
                double kConst = 3 * (Moments[0] - 1) * (Moments[0] - 1)
                                        / ((Moments[0] - 2) * (Moments[0] - 3));
                x4 = this.Variance;
                x4 *= x4;
                return kFact * (m4 * Moments[0] / x4) - kConst;
            }
        }

        /// <summary>
        /// Возвращает коэффициент уклона
        /// </summary>
        public override double Skewness
        {
            get
            {
                if (Moments[0] < 3)
                    return double.NaN;
                double x1 = this.Average;
                double x2 = Moments[2] / Moments[0];
                double x3 = Moments[3] / Moments[0];
                double m3 = x3 + x1 * (2 * x1 * x1 - 3 * x2);
                x1 = this.StandardDeviation;
                x2 = x1 * x1;
                x2 *= x1;
                return m3 * Moments[0] * Moments[0] / (x2 * (Moments[0] - 1)
                                                            * (Moments[0] - 2));
            }
        }

        /// <summary>
        /// Возвращает ненормализированную дисперсию
        /// </summary>
        /// <remarks>необходимо для T-теста</remarks>
        public override double UnnormalizedVariance
        {
            get
            {             
                double average = Average;
                return Moments[2] - average * average * Moments[0];
            }
        }

        /// <summary>
        /// Возвращает дисперсию
        /// </summary>
        /// <remarks>дисперсия включает в себя коррекционный фактор Бесселя</remarks>
        public override double Variance
        {
            get
            {
                if (Moments[0] < 2)
                    return double.NaN;
                double average = this.Average;
                return (Moments[0] / (Moments[0] - 1))
                                * (Moments[2] / Moments[0] - average * average);
            }
        }
    }
}
